/* 
 * $Id: tailq.c 7557b5d4d82b 2012/12/02 09:07:16 pgurumur $
 *
 * Copyright (c) 2006, 2007, 2008 Prabhu Gurumurthy <pgurumur@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "tailq.h"

static char _PyTailQ_ConnectDoc[] = "TAILQ for python";

PyTypeObject PyTailQ_ObjectType = {
   PyObject_HEAD_INIT(NULL)
   0,
   "tailq",
   sizeof(PyTailQ_Object),
   0,

   (destructor)_PyTailQ_Object_DeAlloc,
   NULL,
   (getattrfunc)_PyTailQ_GetAttr,
   (setattrfunc)_PyTailQ_SetAttr,
   NULL,
   NULL,

   NULL,
   NULL,
   NULL,

   NULL,
   NULL,
   NULL,
   NULL,
   NULL,

   NULL,

   Py_TPFLAGS_DEFAULT,

   (char *)_PyTailQ_ConnectDoc,
   (traverseproc)_PyTailQ_Traverse,

   NULL,

   NULL,

   0,

   NULL,
   NULL,

   (struct PyMethodDef *)PyTailQ_Methods,
   (TQ_MEMBER(*))_PyTailQ_ObjectMemberList,
   NULL,
   NULL,

   NULL,
   0,
   NULL,
   0,

   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   0,
   0,
   0
};


/* module hooks */
DL_EXPORT(void)
inittailq(void)
{
   PyObject *module, *dict, *version;

   /* init the module */
   module = Py_InitModule4("tailq", PyTailQ_Methods, _PyTailQ_Doc,
         (PyObject *)NULL, PYTHON_API_VERSION); 
   
   if ((dict = PyModule_GetDict(module)) == NULL)
      Py_FatalError("PyModule_GetDict()");

   /* version of the module */
   version = PyString_FromString(_PyTailQ_Version);
   if (version == NULL || PyDict_SetItemString(dict, _PyTailQ_Version, version))
      Py_FatalError("could not initialize version string");
   
   Py_DECREF(version);
   
   /* Init tailq error */
   PyTailQ_Error = Py_BuildValue("s", "TailQError");
   if (PyTailQ_Error == NULL || 
         PyDict_SetItemString(dict, "error", PyTailQ_Error))
      Py_FatalError("could not initialize tailq");
   
   Py_DECREF(PyTailQ_Error); 

   /* Set object type function */
   PyTailQ_ObjectType.ob_type = &PyType_Type;
   PyTailQ_ObjectType.tp_alloc = PyType_GenericAlloc;
   PyTailQ_ObjectType.tp_new = PyType_GenericNew;
   PyTailQ_ObjectType.tp_free = _PyObject_GC_Del;
   
   if (PyDict_SetItemString(dict, "tailqObject",
            (PyObject *)&PyTailQ_ObjectType))
      Py_FatalError("could not initialize tailq object type"); 
   
   Py_INCREF(&PyTailQ_ObjectType); 
   fflush(stdout);
}

static void
_PyTailQ_Object_DeAlloc(PyTailQ_Object *self)
{
   PyObject *obj;
   if (self->closed == 0) {
      obj = PyTailQ_Close(self, NULL);
      Py_XDECREF(obj);
   }
}

static int
_PyTailQ_Traverse(PyTailQ_Object *self, visitproc visit, void *arg)
{
   int ix;
   int retval = 0;

   if (self->converter) {
      if (!(ix = visit(self->converter, arg)))
         retval = ix;
   }
   
   return retval;
}

static PyObject *
_PyTailQ_GetAttr(PyTailQ_Object *self, char *name)
{
   PyObject *retval = NULL;

   retval = Py_FindMethod(PyTailQ_Methods, (PyObject *)self, name);
   if (retval == NULL)
      PyErr_SetString(PyExc_AttributeError, name);

   return retval;
}

static int
_PyTailQ_SetAttr(PyTailQ_Object *self, char *name, PyObject *obj)
{
   if (obj == NULL) {
      PyErr_SetString(PyExc_AttributeError, "cannot delete attributes");
      return (-1);
   }

   TQ_MEMBER(*list);
   for (list = _PyTailQ_ObjectMemberList; list->name; list++)
      if (strcmp(list->name, name) == 0)
         return PyMember_SetOne((char *)self, list, obj);

   PyErr_SetString(PyExc_AttributeError, name);
   return -1;
}

static PyObject *
PyTailQ_Init(PyObject *self, PyObject *args)
{
   PyTailQ_Object *object = TQ_ALLOC(PyTailQ_Object, PyTailQ_ObjectType);
   if (object == NULL) {
      PyErr_SetString(PyTailQ_Error, "cannot initialize tailq object");
      Py_Exit(1);
   }
   
   self = self;

   object->count = 0;
   object->closed = 0;
   TAILQ_INIT(&object->PyTailQ_Head);
   Py_INCREF(object);

   return (PyObject *)object;
}

static PyObject *
PyTailQ_Close(PyTailQ_Object *self, PyObject *args)
{
   PyObject *retval = NULL;

   if (self) {
      if (self->closed == 0) {
         struct tailq *ptr;
         while (ptr = TAILQ_FIRST(&self->PyTailQ_Head)) {
            TAILQ_REMOVE(&self->PyTailQ_Head, ptr, entries);
            self->count--;
         }
         self->closed = 1;
      
         Py_DECREF(self);
      }
   }

   Py_INCREF(Py_None);
   retval = Py_None;

   return retval;
}

static PyObject *
PyTailQ_Add(PyTailQ_Object *self, PyObject *args)
{
   PyObject *retval = NULL;
   int flag = 0;
   int error = 0;

   if (self) {
      PyObject *key;
      PyObject *value;

      if (args) {
         int ok = PyArg_ParseTuple(args, "OO", &key, &value);
         if (ok) {
            enum TailQ_ValueType type = CheckValueType(value);
            int keypresent = 0;
            struct tailq *ptr, *next_ptr;
            for (ptr = TAILQ_FIRST(&self->PyTailQ_Head); ptr != NULL; 
                  ptr = next_ptr) {
               if (&ptr->key == key) {
                  ptr->value = value;
                  ptr->type = type;
                  keypresent = 1;
                  flag = 1;
               }
               next_ptr = TAILQ_NEXT(ptr, entries);
            }

            if (keypresent == 0) {
               struct tailq *newptr = malloc(sizeof(struct tailq));
               if (newptr) {
                  newptr->key = key;
                  newptr->value = value;
                  newptr->type = type;
                  TAILQ_INSERT_TAIL(&self->PyTailQ_Head, newptr, entries);
                  self->count++;
                  flag = 1;
               }
               else
                  PyErr_SetString(PyTailQ_Error, strerror(errno));
            }

            if (flag)
               retval = Py_BuildValue("d", flag);
            else
               PyErr_SetString(PyTailQ_Error, "cannot add elements to tailq");
         }
         else
            PyErr_SetString(PyTailQ_Error, "missing tailq elements to add");
      }
   }

   return retval;
}

static PyObject *
PyTailQ_Delete(PyTailQ_Object *self, PyObject *args)
{
   PyObject *retval = NULL;
   int flag = 0;

   if (self) {
      PyObject *key;
      PyObject *value;

      if (args)
         PyArg_ParseTuple(args, "OO", &key, &value);

      if (key && value) {
         int matched = 0;
         struct tailq *ptr;
         while (ptr = TAILQ_FIRST(&self->PyTailQ_Head)) {
            if (ptr->key == key)
               TAILQ_REMOVE(&self->PyTailQ_Head, ptr, entries);
               ptr->key = NULL;
               ptr->value = NULL;
               self->count--;
               flag = 1;
               matched = 1;
            }
         }

         if (matched == 0) 
            PyErr_SetString(PyTailQ_Error, "no match");
         else
            retval = Py_BuildValue("d", flag);
      }
   }

   return retval;
}

static PyObject *
PyTailQ_List(PyTailQ_Object *self, PyObject *args)
{
   PyObject *retval = PyDict_New();

   if (self) {
      if (self->count) {
         struct tailq *ptr, *next_ptr;
         for (ptr = TAILQ_FIRST(&self->PyTailQ_Head); ptr != NULL; 
               ptr = next_ptr) {
            if (ptr->value && ptr->key) {
               if (PyDict_SetItem(retval, 
                        Py_BuildValue("s", ptr->key), ptr->value) < 0) {
                  PyErr_SetString(
                        PyTailQ_Error, "cannot insert item into dict");
                  break;
               }
            }
            next_ptr = TAILQ_NEXT(ptr, entries);
         }

         printf("size: %d\n", PyDict_Size(retval));
         assert(PyDict_Size(retval) == self->count);

         Py_DECREF(retval);
      }
   }

   return retval;
}

static int
CheckObjectType(PyObject *object)
{
   enum TailQ_ValueType retval = INVALID;
   if (object) {
      if (PyInt_Check(object))
         retval = INTEGER;
      else if (PyBool_Check(object))
         retval = BOOLEAN;
      else if (PyLong_Check(object))
         retval = LONG;
      else if (PyFloat_Check(object))
         retval = FLOAT;
      else if (PyString_Check(object))
         retval = STRING;
      else if (PyUnicode_Check(object))
         retval = UNICODE;
      else if (PyTuple_Check(object))
         retval = TUPLE;
      else if (PyList_Check(object))
         retval = LIST;
      else if (PyDict_Check(object))
         retval = DICTIONARY;
   }

   return retval;
}
